package org.zhaya.sdk.processors;

import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import io.logz.sawmill.Doc;
import io.logz.sawmill.ProcessResult;
import io.logz.sawmill.Processor;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.utilities.JsonUtils;
import io.logz.sawmill.utilities.MathExpressionProvider;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.zhaya.sdk.util.CalculatorUtil;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@ProcessorProvider(
        type = "math",
        factory = MathProcessor.Factory.class
)
public class MathProcessor implements Processor {
    private static final Pattern MUSTACHE_PATTERN = Pattern.compile("\\{\\{(.+?)\\}\\}");
    private final String targetField;
    private final String expression;
    private final Set<String> variables;

    public MathProcessor(String targetField, Set<String> variables, String expression) {
        this.targetField = Objects.requireNonNull(targetField, "target field cannot be null");
        this.variables = variables;
        this.expression = expression;
    }

    @Override
    public ProcessResult process(Doc doc) {
        Map<String, BigDecimal> variablesMap = Maps.newHashMapWithExpectedSize(variables.size());

        for (String variable : this.variables) {
            if (!doc.hasField(variable)) {
                return ProcessResult.failure(String.format("field [%s] is missing", variable));
            }

            BigDecimal value;
            try {
                value = this.resolveVariable(doc, variable);
            } catch (Exception e) {
                return ProcessResult.failure(String.format("field [%s] is not a number", variable));
            }

            variablesMap.put(variable, value);
        }

        try {
            doc.addField(targetField, operatorVariable(variablesMap));

        } catch (ArithmeticException var6) {
            return ProcessResult.failure("division by zero!");
        }

        return ProcessResult.success();
    }

    private BigDecimal resolveVariable(Doc doc, String variable) throws Exception {
        Object fieldValue = doc.getField(variable);
        return new BigDecimal(String.valueOf(fieldValue));
    }

    /**
     * 对参数进行运算，支持+ - * / % ^运算
     *
     * @return
     */
    private BigDecimal operatorVariable(Map<String, BigDecimal> variablesMap) {
        String finalExpression = this.expression;
        //字符替换成对应的数字进行计算得出结果
        for (String variable : this.variables) {
            finalExpression = finalExpression.replace(variable, MapUtil.getStr(variablesMap, variable));
        }
        return BigDecimal.valueOf(Double.parseDouble(CalculatorUtil.apply(finalExpression)));
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Configuration implements Processor.Configuration {
        private String targetField;
        private String expression;

    }

    @NoArgsConstructor
    public static class Factory implements Processor.Factory {

        @Override
        public MathProcessor create(Map<String, Object> config) {
            Configuration mathConfig = JsonUtils.fromJsonMap(Configuration.class, config);
            String expression = Objects.requireNonNull(mathConfig.getExpression(), "expression cannot be null");
            Set<String> variables = this.findVariables(expression);
            String trimMustacheExpression = this.trimMustache(expression);
            MathExpressionProvider mathExpressionProvider = new MathExpressionProvider(trimMustacheExpression, variables);
            if (!mathExpressionProvider.provide().validate(false).isValid()) {
                throw new ProcessorConfigurationException(String.format("invalid expression [%s]", expression));
            } else {
                return new MathProcessor(Objects.requireNonNull(mathConfig.getTargetField()), variables, trimMustacheExpression);
            }
        }

        private String trimMustache(String expression) {
            return expression.replaceAll("(\\{\\{|\\}\\})", StringUtils.EMPTY);
        }

        private Set<String> findVariables(String expression) {
            Set<String> variables = Sets.newHashSet();
            Matcher matcher = MUSTACHE_PATTERN.matcher(expression);

            while (matcher.find()) {
                variables.add(matcher.group(1));
            }

            return variables;
        }
    }
}